package bma.common.netty.protobuf.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferOutputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;

import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.io.IOUtil;
import bma.common.netty.handler.ChannelHandlerFramed;
import bma.common.netty.protobuf.core.BaseProtos.PError;
import bma.common.netty.protobuf.core.BaseProtos.PHeader;
import bma.common.netty.protobuf.core.BaseProtos.PHeader.MessageType;
import bma.common.netty.protobuf.core.BaseProtos.PRequest;
import bma.common.netty.protobuf.core.BaseProtos.PResult;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message;

public class PUtil {

	static final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(PUtil.class);

	public static ChannelBuffer writeMessage(GeneratedMessage msg) {
		if (msg != null) {
			ChannelBuffer r = ChannelBuffers.dynamicBuffer();
			OutputStream out = new ChannelBufferOutputStream(r);
			try {
				msg.writeDelimitedTo(out);
			} catch (IOException err) {
				ExceptionUtil.throwRuntime(err);
			} finally {
				IOUtil.close(out);
			}
			return r;
		}
		return ChannelBuffers.EMPTY_BUFFER;
	}

	public static ChannelBuffer writeObject(Object obj) {
		if (obj == null)
			return ChannelBuffers.EMPTY_BUFFER;
		if (obj instanceof GeneratedMessage) {
			GeneratedMessage msg = (GeneratedMessage) obj;
			return writeMessage(msg);
		} else if (obj instanceof PObject) {
			PObject o = (PObject) obj;
			ChannelBuffer r = ChannelBuffers.dynamicBuffer();
			OutputStream out = new ChannelBufferOutputStream(r);
			try {
				o.writeTo(out);
			} catch (IOException err) {
				ExceptionUtil.throwRuntime(err);
			} finally {
				IOUtil.close(out);
			}
			return r;
		} else if (obj instanceof ChannelBuffer) {
			return (ChannelBuffer) obj;
		} else {
			throw new IllegalArgumentException("data type error ("
					+ obj.getClass().getName() + ")");
		}
	}

	public static ChannelBuffer buildChannelBuffer(PHeader header,
			PRequest req, Object data) {
		ChannelBuffer headerBuffer = writeMessage(header);
		ChannelBuffer requestBuffer = writeMessage(req);
		ChannelBuffer dataBuffer = writeObject(data);

		int len = headerBuffer.writerIndex() + requestBuffer.writerIndex()
				+ dataBuffer.writerIndex();
		byte[] i32buf = new byte[4];
		ChannelHandlerFramed.encodeFrameSize(len, i32buf);

		ChannelBuffer outbuffer = ChannelBuffers.wrappedBuffer(
				ChannelBuffers.wrappedBuffer(i32buf), headerBuffer,
				requestBuffer, dataBuffer);
		return outbuffer;
	}

	public static void write(Channel ch, PHeader header, PRequest req,
			Object data, boolean closed) {
		if (ch.isOpen()) {
			ChannelBuffer outbuffer = buildChannelBuffer(header, req, data);
			ChannelFuture cf = ch.write(outbuffer);
			if (closed) {
				cf.addListener(ChannelFutureListener.CLOSE);
			}
		}
	}

	public static PHeader responseHeader(PHeader requestHeader, MessageType type) {
		return PHeader.newBuilder().setId(requestHeader.getId())
				.setVersion(requestHeader.getVersion())
				.setType(type != null ? type : MessageType.E_RESPONSE).build();
	}

	public static PRequest request(String sessionId, String target, String name) {
		PRequest.Builder b = PRequest.newBuilder();
		if (sessionId != null) {
			b.setSessionId(sessionId);
		}
		b.setTarget(target);
		b.setName(name);
		return b.build();
	}

	public static PError error404(String msg) {
		return PError.newBuilder().setCode(404).setType("notFound")
				.setMessage(msg).build();
	}

	public static <TYPE extends GeneratedMessage> TYPE to(PResponseContext rep,
			Class<TYPE> cls, Message.Builder builder) {
		return to(rep.getInputStream(), cls, builder);
	}

	public static <TYPE extends GeneratedMessage> TYPE to(PRequestContext req,
			Class<TYPE> cls, Message.Builder builder) {
		return to(req.getInputStream(), cls, builder);
	}

	public static <TYPE extends GeneratedMessage> TYPE to(InputStream in,
			Class<TYPE> cls, Message.Builder b) {
		try {
			b.mergeDelimitedFrom(in);
			return cls.cast(b.build());
		} catch (Exception e) {
			throw ExceptionUtil.throwRuntime(e);
		}
	}

	public static String toString(PHeader h) {
		StringBuilder sb = new StringBuilder(200);
		sb.append("ver=").append(h.getVersion()).append(";");
		sb.append("id=").append(h.getId()).append(";");
		sb.append("type=").append(h.getType()).append(";");
		return sb.toString();
	}

	public static String toString(PRequest r) {
		StringBuilder sb = new StringBuilder(200);
		sb.append("sid=").append(r.getSessionId()).append(";");
		sb.append("tar=").append(r.getTarget()).append(";");
		sb.append("name=").append(r.getName()).append(";");
		return sb.toString();
	}

	public static PResult result() {
		return PResult.newBuilder().setStatus(1).build();
	}

	public static PResult result(boolean r) {
		return PResult.newBuilder().setBvalue(r).setStatus(1).build();
	}

	public static PResult result(int r) {
		return PResult.newBuilder().setIvalue(r).setStatus(1).build();
	}

	public static PResult result(long r) {
		return PResult.newBuilder().setLvalue(r).setStatus(1).build();
	}

	public static PResult result(String r) {
		return PResult.newBuilder().setSvalue(r).setStatus(1).build();
	}

	public static PResult result(double r) {
		return PResult.newBuilder().setDvalue(r).setStatus(1).build();
	}
}
